package com.lpy.util;

import com.intellij.lang.jvm.types.JvmReferenceType;
import com.intellij.lang.jvm.types.JvmType;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;

import java.io.IOException;
import java.util.List;

/**
 * @author lipengyu
 */
public class GotoUtil {


    public static final String REPOSITORY = "Repository";

    public static final String TEMPLATE_QUERY = "com.slyak.spring.jpa.TemplateQuery";

    public static final String GENERIC_JPA_REPOSITORY = "GenericJpaRepository";

    public static final String CRUD_REPOSITORY = "CrudRepository";

    public static final String JPA_REPOSITORY = "JpaRepository";

    public static final String SFTL_SQL_ID_PREFIX = "-- ";

    /**
     * 判断sftl中是否包含id的行数
     *
     * @param file         给定文件
     * @param methodName 给定的匹配内容
     * @return 行号
     */
    public static Integer getSftlLineNumber(PsiFile file, String methodName) {
        if (isBlank(methodName)) {
            return null;
        }

        String[] lineContents = file.getText().split("\n");
        for (int i = 0; i < lineContents.length; i++) {
            String lineContent = lineContents[i];
            if (isNotBlank(lineContent)) {
                String trim = lineContent.trim();
                if (trim.equals(SFTL_SQL_ID_PREFIX + methodName)) {
                    return i;
                }
            }
        }
        return null;
    }

    /**
     * 获取匹配内容在给定文件的行号
     *
     * @param file         给定文件
     * @param matchContent 给定的匹配内容
     * @return 行号
     */
    public static Integer getRepositoryLineNumber(PsiFile file, String matchContent) {
        if (isBlank(matchContent)) {
            return null;
        }

        String[] lineContents = file.getText().split("\n");
        for (int i = 0; i < lineContents.length; i++) {
            if (isNotBlank(lineContents[i])) {
                if (lineContents[i].contains(" " + matchContent + "(")) {
                    return i;
                }
            }
        }
        return null;
    }

    /**
     * 判断sftl中是否包含该id
     *
     * @param file         给定文件
     * @param methodName 给定的匹配内容
     * @return 行号
     */
    public static boolean containsSqlId(PsiFile file, String methodName) {
        if (isBlank(methodName)) {
            return false;
        }
        String[] lineContents = file.getText().split("\n");
        for (String lineContent : lineContents) {
            if (isNotBlank(lineContent)) {
                String trim = lineContent.trim();
                if (trim.equals(SFTL_SQL_ID_PREFIX + methodName)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 定位文件，且在内容匹配时精确到行
     *
     * @param project      当前工程
     * @param file         给定的匹配文件
     * @return 是/否成功定位
     */
    public static boolean doLocateMethod(Project project, PsiFile file, int offset) {
        OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file.getVirtualFile());
        // 打开文件
        Editor editor = FileEditorManager.getInstance(project)
                .openTextEditor(descriptor, true);
        if (editor == null) {
            return false;
        }
        // 定位
        CaretModel caretModel = editor.getCaretModel();
        // 移动到目标行
        caretModel.moveToOffset(offset);
        // 将目标行滚动到屏幕中央
        editor.getScrollingModel().scrollToCaret(ScrollType.CENTER_DOWN);
        // 移除如果有选择的内容
        editor.getSelectionModel().removeSelection();

        return true;
    }

    /**
     * 定位文件，且在内容匹配时精确到行
     *
     * @param project      当前工程
     * @param matchContent 匹配内容
     * @param file         给定的匹配文件
     * @return 是/否成功定位
     */
    public static boolean doLocate(Project project, String matchContent, PsiFile file) {
        return doLocate(project, matchContent, file, null);
    }

    /**
     * 定位文件，且在内容匹配时精确到行
     *
     * @param project      当前工程
     * @param matchContent 匹配内容
     * @param file         给定的匹配文件
     * @return 是/否成功定位
     */
    public static boolean doLocateSftl(Project project, String matchContent, PsiFile file) {
        return doLocate(project, getSftlLineNumber(file, matchContent), file, 3);
    }

    /**
     * 定位文件，且在内容匹配时精确到行
     *
     * @param project      当前工程
     * @param matchContent 匹配内容
     * @param file         给定的匹配文件
     * @return 是/否成功定位
     */
    public static boolean doLocate(Project project, String matchContent, PsiFile file, Integer column) {
        return doLocate(project, getRepositoryLineNumber(file, matchContent), file, column);
    }

    /**
     * 定位文件，且在内容匹配时精确到行
     *
     * @param project      当前工程
     * @param lineNumber    行号
     * @param file         给定的匹配文件
     * @return 是/否成功定位
     */
    public static boolean doLocate(Project project, Integer lineNumber, PsiFile file, Integer column) {
        if (lineNumber == null) {
            return false;
        }
        OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file.getVirtualFile());
        // 打开文件
        Editor editor = FileEditorManager.getInstance(project)
                .openTextEditor(descriptor, true);
        if (editor == null) {
            return false;
        }
        // 定位
        CaretModel caretModel = editor.getCaretModel();
        LogicalPosition logicalPosition = caretModel.getLogicalPosition();
        logicalPosition.leanForward(true);
        // 移动到目标行
        if (column == null) {
            column = logicalPosition.column;
        }
        caretModel.moveToLogicalPosition(new LogicalPosition(lineNumber, column));
        // 将目标行滚动到屏幕中央
        editor.getScrollingModel().scrollToCaret(ScrollType.CENTER_DOWN);
        // 移除如果有选择的内容
        editor.getSelectionModel().removeSelection();
        return true;
    }

    /**
     * 定位文件在最后一行
     *
     * @param project      当前工程
     * @param file         给定的匹配文件
     * @return 是/否成功定位
     */
    public static boolean doLocateLast(Project project, PsiFile file) {
        OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file.getVirtualFile());
        // 打开文件
        Editor editor = FileEditorManager.getInstance(project)
                .openTextEditor(descriptor, true);
        if (editor == null) {
            return false;
        }
        // 定位
        CaretModel caretModel = editor.getCaretModel();
        LogicalPosition logicalPosition = caretModel.getLogicalPosition();
        logicalPosition.leanForward(true);
        caretModel.moveToOffset(editor.getDocument().getText().length());
        // 将目标行滚动到屏幕中央
        editor.getScrollingModel().scrollToCaret(ScrollType.CENTER_DOWN);
        // 移除如果有选择的内容
        editor.getSelectionModel().removeSelection();
        return true;
    }

    public static boolean isEntitySftl(PsiFile psiFile) {
        return psiFile != null && psiFile.getName().endsWith(".sftl");
    }

    /**
     * 判断是否是一个 jpaExtra 方法：
     * 1 给定元素必须是 PsiMethod
     * 2 给定元素的方法上必须使用了 @TemplateQuery 注解声明 Url
     *
     * @param psiElement 给定元素
     * @return 是/否是 Feign 方法
     */
    public static boolean isJpaExtraMethod(PsiElement psiElement) {
        return psiElement instanceof PsiMethod
                && (getAnnotationAtMethod(psiElement, GotoUtil.TEMPLATE_QUERY) != null);
    }

    /**
     * 获取方法上的注解
     *
     * @param psiElement              给定元素，该元素必须为方法
     * @param annotationQualifiedName 注解的限定名，即全类名
     * @return 注解
     */
    public static PsiAnnotation getAnnotationAtMethod(PsiElement psiElement, String annotationQualifiedName) {
        if (!(psiElement instanceof PsiMethod)) {
            return null;
        }

        PsiMethod psiMethod = (PsiMethod) psiElement;

        // 根据注解限定名获取指定注解
        return psiMethod.getAnnotation(annotationQualifiedName);
    }

    public static boolean isNotBlank(final CharSequence cs) {
        return !isBlank(cs);
    }

    public static boolean isBlank(final CharSequence cs) {
        int strLen;
        if (cs == null || (strLen = cs.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }


    @Nullable
    public static String getEntityClassName(PsiClass currentPsiClass) {
        JvmReferenceType[] interfaceTypes = currentPsiClass.getInterfaceTypes();
        if (interfaceTypes.length == 0) {
            return null;
        }
        JvmReferenceType interfaceType = null;
        for (JvmReferenceType type : interfaceTypes) {
            if (type.getName().endsWith("Repository")) {
                interfaceType = type;
                break;
            }
        }
        if (interfaceType == null) {
            return null;
        }
        Iterable<JvmType> jvmTypes = interfaceType.typeArguments();
        if (!jvmTypes.iterator().hasNext()) {
            return null;
        }
        JvmReferenceType type = (JvmReferenceType) jvmTypes.iterator().next();
        if (type == null) {
            return null;
        }
        return type.getName();
    }

    @Nullable
    public static PsiIdentifier getPsiIdentifier(String methodName, PsiJavaFile t) {
        PsiClass[] classes = t.getClasses();
        if (classes.length == 0) {
            return null;
        }
        PsiClass aClass = classes[0];
        PsiMethod[] methodsByName = aClass.findMethodsByName(methodName, false);
        if (methodsByName.length == 0) {
            return null;
        }
        PsiMethod psiMethod = methodsByName[0];
        return psiMethod.getNameIdentifier();
    }

    public static void createFileAndJump(@NotNull Project project, Module module, String entityClassName, String sqlId) {

        // 获取当前模块的resources目录
        ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
        List<VirtualFile> sourceRoots = rootManager.getSourceRoots(JavaModuleSourceRootTypes.RESOURCES);
        if (sourceRoots.size() == 0) {
            new Notification("com.lpy.jpaExtraX", "JpaExtraX warning", "Please mark your resources directory as resources root.", NotificationType.WARNING).notify(project);
            return;
        }
        VirtualFile resourceDirectory = sourceRoots.get(0);
        // 获取resources目录下的sqls目录
        VirtualFile sqlsVirtualFile = resourceDirectory.findChild("sqls");
        if (sqlsVirtualFile == null) {
            try {
                sqlsVirtualFile = resourceDirectory.createChildDirectory(module, "sqls");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (sqlsVirtualFile == null) {
            return;
        }
        PsiDirectory sqlsPsiDirectory = PsiManager.getInstance(project).findDirectory(sqlsVirtualFile);
        if (sqlsPsiDirectory == null) {
            return;
        }
        // 没有找到映射文件，则创建
        PsiFile entitySftlPsiFile = PsiFileFactory.getInstance(project)
                .createFileFromText(entityClassName + ".sftl",
                        FileTypeManager.getInstance().getStdFileType("FTL"), sqlId + "\n");
        // 添加sftl到sqls目录下，此步操作才会触发文件保存
        PsiFile newFile = (PsiFile) sqlsPsiDirectory.add(entitySftlPsiFile);
        // 执行跳转
        GotoUtil.doLocateLast(project, newFile);
    }
}
